/**
******************************************************************************************
* @file LCDProjector.c
* @brief Management of the LCD and dualButton bricklets. Management of all user interface.
* @date 14 avril 2015
* @author Benjamin Lancinot
*****************************************************************************************/
/* COPYRIGHT ****************************************************************************
*****************************************************************************************
* Copyright (c) 2015, Audrey LOISEAU and Benjamin LANCINOT.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************************************/

#include "LCDProjector.h"

/*******************************************************************************************
* PROTOTYPES
********************************************************************************************/

void LCDProjector_start(LCDProjector *, int*);

void LCDProjector_stop(LCDProjector *);

void LCDProjector_destroyUtilities(LCDProjector*);

void LCDProjector_destroy(LCDProjector** );

void LCDProjector_notify(LCDProjector*, int , void*);

LCDMessage _takeOneMessageFromBAL(int);

void _putOneMessageOnBAL(int, MQ_msg*);

void cb_state_changed(uint8_t , uint8_t, uint8_t, uint8_t, LCDProjector *);

void *LCDProjector_run(LCDProjector *);

/*******************************************************************************************
* FUNCTIONS
********************************************************************************************/

/**
 * @brief Constructor
 * @param ipcon Ipconnection for link bricklet to the rest of bricks/bricklets
 * @return Pointer to a new LCDProjector
 **/
LCDProjector* LCDProjector_create(IPConnection ipcon){
	LCDProjector* This = (LCDProjector *) calloc(1, sizeof(*This));

	//Creation of the LCD
	LCD20x4 lcd;
	lcd_20x4_create(&lcd, "qHL", &ipcon);
        This->lcdSensor = lcd;

        //Creation of the Dual Button
        DualButton db;
	dual_button_create(&db, "j3H", &ipcon);
        This->dualButtonSensor = db;

        //Init function
	This->start = LCDProjector_start;
	This->stop = LCDProjector_stop;
	This->destroy = LCDProjector_destroy;

        // create a message queue and analyze errors if there are.
	This->BAL_id = msgget(IPC_PRIVATE,0600|IPC_CREAT|IPC_EXCL);
	if (This->BAL_id < 0) {
		perror(strerror(errno));
		printf("failed to create message queue with msgqid = %d\n", This->BAL_id);
	}

	//Init it as an observer
	This->observer = observerNew(This, (void (*)(void*, int, void*)) LCDProjector_notify);

        //Init display
        This->isDisplaying = FALSE;
        This->displayMode = ECRAN_GENERAL;

	return This;
}


/**
 * @brief Run the thread which permit to display on LCD
 * @param This Pointer to the wanted LCDProjector
 * @param carryOn Pointer to the value which permit to switch off NannyBear
 **/
void LCDProjector_start(LCDProjector *This, int * carryOn){
	This->carryOn = TRUE;
        This->nannyIsAlive = carryOn;
        dual_button_set_led_state(&This->dualButtonSensor, 2, 2);

	//Creation of the thread which permitt to send from the Robot
	pthread_create(&This->lcdThread, NULL,(void *) &LCDProjector_run,(LCDProjector *)This);
}

/**
 * @brief Stop the thread which permit to display on LCD
 * @param This Pointer to the wanted LCDProjector
 **/
void LCDProjector_stop(LCDProjector *This){
	This->carryOn = FALSE;
	pthread_join(This->lcdThread,NULL);
}



/**
 * @brief Destroy all of the components of LCDProjector
 * @param This Pointer to the wanted LCDProjector
 **/
void LCDProjector_destroyUtilities(LCDProjector* This){

	int rc;
        //Close the message queue
	rc=msgctl(This->BAL_id,IPC_RMID,NULL);
	if (rc < 0) {
		perror( strerror(errno) );
		printf("msgctl (return queue) failed, rc=%d\n", rc);
	}

        //destruct associate object
        This->observer->destroy(This->observer);
        lcd_20x4_destroy(&This->lcdSensor);
        dual_button_destroy(&This->dualButtonSensor);
}


/**
 * @brief Destructor
 * @param This Double pointer to the wanted LCDProjector
 **/
void LCDProjector_destroy(LCDProjector** This){

	LCDProjector_destroyUtilities(* This);
        //destruct itself
	if (*This) {
		free(*This);
		*This = NULL;
	}


}



/**
 * @brief Observer Method called upon the receiption of an incoming event.This method react with thE type of information which have received
 * @param This Pointer to the wanted LCDProjector
 * @param numero Value of the information type of the data received
 * @param subject Pointer to the subject to use
 **/
void LCDProjector_notify(LCDProjector* This, int numero, void* subject) {
	MQ_msg * msgMQ = calloc(1,sizeof(MQ_msg));
        strcpy(msgMQ->message.valeur, " ");
	msgMQ->mtype = 1;
	msgMQ->message.carryOn=TRUE;
	msgMQ->message.action=NV;


	if (numero == 1){
		MercureLover* temp = (MercureLover*) subject;
                sprintf(msgMQ->message.valeur," %0.1f C",temp->temperature);
		msgMQ->message.action = TEMPERATURE;

        }
        if (numero == 2){
		HumidityTester * temp = (HumidityTester*) subject;
		sprintf(msgMQ->message.valeur," %0.1f %%",temp->humidity);
		msgMQ->message.action = HUMIDITY;
        }
        if (numero == 4){
		MercureController * temp = (MercureController*) subject;
                msgMQ->message.action = CRITICALITY_TEMPERATURE;
		if (temp->criticality == 2.0 || temp->criticality == 3.0){
			strcpy(msgMQ->message.valeur, "!");
                }
                else if (temp->criticality == 4.0 || temp->criticality == 5.0){
                	strcpy(msgMQ->message.valeur, "!!");
                }
                else {
			strcpy(msgMQ->message.valeur, "  ");
                }
	}
	if (numero == 5){
		HumidityController * temp = (HumidityController*) subject;
		msgMQ->message.action = CRITICALITY_HUMIDITY;
		if (temp->criticality == 2.0 || temp->criticality == 3.0){
			strcpy(msgMQ->message.valeur, "! ");

                }
                else if (temp->criticality == 4.0 || temp->criticality == 5.0){
                	strcpy(msgMQ->message.valeur, "!!");
                }
                else {
			strcpy(msgMQ->message.valeur, "  ");
                }
	}
        if (numero == 6){
		ConnectionState * temp = (ConnectionState*) subject;
		strcpy(msgMQ->message.valeur, temp->connectionStatus);
		msgMQ->message.action = CONNECTION_STATUS;
        }
        if (numero == 7){
		AverageMaker * temp = (AverageMaker*) subject;
		if(temp->daddy == TEMPERATURE ){
			sprintf(msgMQ->message.valeur," %0.1f C",temp->averageNumerator / temp->totalTime);
			msgMQ->message.action = AVERAGE_TEMPERATURE;
		}else{
			sprintf(msgMQ->message.valeur," %0.1f %%",temp->averageNumerator / temp->totalTime);
			msgMQ->message.action = AVERAGE_HUMIDITY;
		}
        }

        _putOneMessageOnBAL(This->BAL_id, msgMQ);
        free(msgMQ);
}


/**
 * @brief Recover a message to display on the message queue
 * @param BAL_id Value of the message queue id
 * @return The message to display on the LCD
 **/
LCDMessage _takeOneMessageFromBAL(int BAL_id){
	int rc;

        MQ_msg new_msgMQ;
        strcpy(new_msgMQ.message.valeur, "");
	new_msgMQ.mtype = 0;
	new_msgMQ.message.carryOn=FALSE;
	new_msgMQ.message.action=NV;

	// read the message from queue
	rc = msgrcv(BAL_id, &new_msgMQ, sizeof(new_msgMQ.message), 0, IPC_NOWAIT);

        if (rc != -1){
		new_msgMQ.message.carryOn=TRUE;
	}

        return new_msgMQ.message;
}


/**
 * @brief Add a message to display on the message queue
 * @param BAL_id Value of the message queue id
 * @param msgMQ Pointer to a Message to add
 **/
void _putOneMessageOnBAL(int BAL_id, MQ_msg * msgMQ) {

	// send the message to queue
	if( msgsnd(BAL_id, msgMQ, sizeof(msgMQ->message), 0) < 0) {
		perror( strerror(errno) );
	}
}



/**
 * @brief Callback function for dualbutton. Permitt to switch screens and switch off NannyBear or the LCD
 * @param button_l Value of the left button
 * @param button_r Value of the right button
 * @param led_l Value of the left led
 * @param led_r Value of the right led
 * @param This Pointer to the wanted LCDProjector
 **/
void cb_state_changed(uint8_t button_l, uint8_t button_r,
                      uint8_t led_l, uint8_t led_r,
                      LCDProjector * This) {
	(void)led_l; // avoid unused parameter warning
	(void)led_r; // avoid unused parameter warning

	if(button_l == DUAL_BUTTON_BUTTON_STATE_PRESSED) {
		if (This->isDisplaying == FALSE){
			This->isDisplaying = TRUE;
			This->displayMode = ECRAN_GENERAL;
                        //switch on led_r and switch off led_l
                        dual_button_set_led_state(&This->dualButtonSensor, 3, 2);
		}else{
                        if (This->displayMode ==  ECRAN_KILLDISPLAY){
				This->isDisplaying = FALSE;
                                dual_button_set_led_state(&This->dualButtonSensor, 3, 3);
			}
			else if (This->displayMode == ECRAN_KILLNANNY){
				*This->nannyIsAlive = FALSE;
			}
			else if (This->displayMode == ECRAN_CANCEL){
				This->displayMode = ECRAN_GENERAL;
			}
		}

	}

	if(button_r == DUAL_BUTTON_BUTTON_STATE_PRESSED) {
	   	if (This->isDisplaying == FALSE){
			This->isDisplaying = TRUE;
			This->displayMode = ECRAN_GENERAL;
                        //switch on led_r and switch off led_l
                        dual_button_set_led_state(&This->dualButtonSensor, 3, 2);
		}else{
			if (This->displayMode == ECRAN_GENERAL){
				This->displayMode = ECRAN_TEMPERATURE;
			}
			else if (This->displayMode == ECRAN_TEMPERATURE){
				This->displayMode = ECRAN_HUMIDITY;
			}
			else if (This->displayMode == ECRAN_HUMIDITY){
				This->displayMode =  ECRAN_KILLDISPLAY;
			}
			else if (This->displayMode ==  ECRAN_KILLDISPLAY){
				This->displayMode =  ECRAN_KILLNANNY;
			}
			else if (This->displayMode == ECRAN_KILLNANNY){
				This->displayMode =  ECRAN_CANCEL;
			}
			else{
				This->displayMode = ECRAN_KILLDISPLAY;
			}
		}

	}

}



/**
 * @brief Function use by lcdThread for displaying message on lcd screen
 * @param This Pointer to the wanted LCDProjector
 * @return The final value for the thread
 **/
void *LCDProjector_run(LCDProjector *This){
        LCDMessage message;
        int state=0;
        int previousState=0;
        char temperature[10]= "UNKNOW";
        char humidity [10]= "UNKNOW";
        char connectionStatus [10]= "NO";
	char tempCriticality [10]= " ";
        char humCriticality [10]= " ";
	char tempAverage [10]= " ";
        char humAverage [10]= " ";

	// Register state changed callback to function cb_state_changed of the dualbutton
	dual_button_register_callback(&This->dualButtonSensor,
	                              DUAL_BUTTON_CALLBACK_STATE_CHANGED,
	                              (void *)cb_state_changed,
	                              (LCDProjector *)This);



	while(This->carryOn){

		//Recover the message on the message queue
		message = _takeOneMessageFromBAL(This->BAL_id);

		if (message.carryOn == TRUE ){
			if (message.action == TEMPERATURE){
				strcpy(temperature,message.valeur);
			}
			if (message.action == HUMIDITY){
				strcpy(humidity,message.valeur);
			}
			if (message.action == CRITICALITY_TEMPERATURE){
				strcpy(tempCriticality, message.valeur);
			}
			if (message.action == CRITICALITY_HUMIDITY){
				strcpy(humCriticality, message.valeur);
			}
			if (message.action == CONNECTION_STATUS){
				strcpy(connectionStatus,message.valeur);
			}
			if (message.action == AVERAGE_TEMPERATURE){
				strcpy(tempAverage,message.valeur);
			}
                        if (message.action == AVERAGE_HUMIDITY){
				strcpy(humAverage,message.valeur);
			}



		}



		if (This->isDisplaying == TRUE){

                        lcd_20x4_backlight_on(&This->lcdSensor);
			switch (state){

			case ECRAN_GENERAL:
				lcd_20x4_clear_display(&This->lcdSensor);
				lcd_20x4_backlight_on(&This->lcdSensor);
				lcd_20x4_write_line(&This->lcdSensor, 0, 3, "Connected:");
				lcd_20x4_write_line(&This->lcdSensor, 0, 14, connectionStatus);
				lcd_20x4_write_line(&This->lcdSensor, 1, 0, "____________________");
				lcd_20x4_write_line(&This->lcdSensor, 2, 3, "Temp:");
				lcd_20x4_write_line(&This->lcdSensor, 2, 0, tempCriticality);
				lcd_20x4_write_line(&This->lcdSensor, 2, 8, temperature);
				lcd_20x4_write_line(&This->lcdSensor, 3, 3, "Hum :");
				lcd_20x4_write_line(&This->lcdSensor, 3, 0, humCriticality);
				lcd_20x4_write_line(&This->lcdSensor, 3, 8, humidity);
                                previousState=ECRAN_GENERAL;
                                state = CHANGE_DATA;
				break;
		        case ECRAN_TEMPERATURE:
	                        lcd_20x4_clear_display(&This->lcdSensor);
				lcd_20x4_write_line(&This->lcdSensor, 0, 4, "TEMPERATURE");
				lcd_20x4_write_line(&This->lcdSensor, 0, 0, tempCriticality);
				lcd_20x4_write_line(&This->lcdSensor, 1, 0, "____________________");
				lcd_20x4_write_line(&This->lcdSensor, 2, 0, "Act:");
				lcd_20x4_write_line(&This->lcdSensor, 2, 5, temperature);
				lcd_20x4_write_line(&This->lcdSensor, 3, 0, "Avr:");
				lcd_20x4_write_line(&This->lcdSensor, 3, 5, tempAverage);
				previousState=ECRAN_TEMPERATURE;
                                state = CHANGE_DATA;
				break;
			case ECRAN_HUMIDITY:
				lcd_20x4_clear_display(&This->lcdSensor);
				lcd_20x4_write_line(&This->lcdSensor, 0, 6, "HUMIDITY");
				lcd_20x4_write_line(&This->lcdSensor, 0, 0, humCriticality);
				lcd_20x4_write_line(&This->lcdSensor, 1, 0, "____________________");
				lcd_20x4_write_line(&This->lcdSensor, 2, 0, "Act:");
				lcd_20x4_write_line(&This->lcdSensor, 2, 5, humidity);
				lcd_20x4_write_line(&This->lcdSensor, 3, 0, "Avr:");
				lcd_20x4_write_line(&This->lcdSensor, 3, 5, humAverage);
				previousState=ECRAN_HUMIDITY;
                                state = CHANGE_DATA;
				break;
			case CHANGE_DATA:
				if (message.action == TEMPERATURE){
					if (previousState == ECRAN_GENERAL){
						lcd_20x4_write_line(&This->lcdSensor, 2, 8, temperature);
					}
					if (previousState == ECRAN_TEMPERATURE){
						lcd_20x4_write_line(&This->lcdSensor, 2, 5, temperature);
					}
				}
				if (message.action == HUMIDITY){
					if (previousState == ECRAN_GENERAL){
						lcd_20x4_write_line(&This->lcdSensor, 3, 8, humidity);
					}
					if (previousState == ECRAN_HUMIDITY){
						lcd_20x4_write_line(&This->lcdSensor, 2, 5, humidity);
					}
				}
				if (message.action == CRITICALITY_TEMPERATURE){
					if (previousState == ECRAN_GENERAL){
						lcd_20x4_write_line(&This->lcdSensor, 2, 0, tempCriticality);
					}
					if (previousState == ECRAN_TEMPERATURE){
						lcd_20x4_write_line(&This->lcdSensor, 0, 0, tempCriticality);
					}
				}
				if (message.action == CRITICALITY_HUMIDITY){
					if (previousState == ECRAN_GENERAL){
						lcd_20x4_write_line(&This->lcdSensor, 3, 0, humCriticality);
					}
					if (previousState == ECRAN_HUMIDITY){
						lcd_20x4_write_line(&This->lcdSensor, 0, 0, humCriticality);
					}
				}
				if (message.action == CONNECTION_STATUS){
					if (previousState == ECRAN_GENERAL){
						lcd_20x4_write_line(&This->lcdSensor, 0, 14, connectionStatus);
					}

				}
				if (message.action == AVERAGE_TEMPERATURE){
					if (previousState == ECRAN_TEMPERATURE){
						lcd_20x4_write_line(&This->lcdSensor, 3, 5, tempAverage);
					}
				}
				if (message.action == AVERAGE_HUMIDITY){
					if (previousState == ECRAN_HUMIDITY){
						lcd_20x4_write_line(&This->lcdSensor, 3, 5, humAverage);
					}
				}
                                if ( This->displayMode != previousState){
					state = This->displayMode;
                                }
				break;
			case  ECRAN_KILLDISPLAY:
                                if(previousState !=ECRAN_KILLDISPLAY){
					lcd_20x4_clear_display(&This->lcdSensor);
					lcd_20x4_write_line(&This->lcdSensor, 0, 0, "- SCREEN OFF");
					lcd_20x4_write_line(&This->lcdSensor, 1, 2, "NANNY OFF");
					lcd_20x4_write_line(&This->lcdSensor, 2, 0, "____________________");
					lcd_20x4_write_line(&This->lcdSensor, 3, 2, "CANCEL");
					previousState=ECRAN_KILLDISPLAY;
                                }
                                if ( This->displayMode != previousState){
					state = This->displayMode;
                                }
				break;
			case ECRAN_KILLNANNY:
                                if(previousState !=ECRAN_KILLNANNY){
					lcd_20x4_write_line(&This->lcdSensor, 0, 0, " ");
					lcd_20x4_write_line(&This->lcdSensor, 1, 0, "-");
                                        previousState =ECRAN_KILLNANNY;
				}
                                if ( This->displayMode != previousState){
					state = This->displayMode;
                                }
				break;
			case ECRAN_CANCEL:
				if(previousState !=ECRAN_CANCEL){
					lcd_20x4_write_line(&This->lcdSensor, 1, 0, " ");
					lcd_20x4_write_line(&This->lcdSensor, 3, 0, "-");
                                        previousState =ECRAN_CANCEL;
				}
                                if ( This->displayMode != previousState){
					state = This->displayMode;
                                }
				break;
			default:
				printf("Error in switch LCD\n");
				break;
			}
		}
		else{
			lcd_20x4_clear_display(&This->lcdSensor);
			lcd_20x4_backlight_off(&This->lcdSensor);
                        dual_button_set_led_state(&This->dualButtonSensor, 2, 2);
                        state = ECRAN_GENERAL;
		}
		usleep(100000);
	}
        //switch off led_r and switch off led_l, clear the display of lcd
	lcd_20x4_clear_display(&This->lcdSensor);
        dual_button_set_led_state(&This->dualButtonSensor, 3, 3);
	lcd_20x4_backlight_off(&This->lcdSensor);
	return 0;
}
